package cn.bby.query.server.infrastructure.db.repository;

import cn.bby.jtk.bean.util.FastBeanUtil;
import cn.bby.query.base.model.dto.ColumnMetaInfo;
import cn.bby.query.base.model.dto.InterfaceMetaInfo;
import cn.bby.query.base.model.dto.TableMetaInfo;
import cn.bby.query.server.domain.agg.metadata.entity.InterfaceInfo;
import cn.bby.query.server.domain.agg.metadata.entity.valobj.ColumnInfo;
import cn.bby.query.server.domain.agg.metadata.repository.DsSettingsRepository;
import cn.bby.query.server.domain.agg.metadata.repository.InterfaceInfoRepository;
import cn.bby.query.server.domain.share.constant.InterfaceStatus;
import cn.bby.query.server.infrastructure.db.entity.InterfaceColumn;
import cn.bby.query.server.infrastructure.db.entity.InterfaceInfoPO;
import cn.bby.query.server.infrastructure.db.entity.InterfaceSubTable;
import cn.bby.query.server.infrastructure.db.mapper.InterfaceColumnMapper;
import cn.bby.query.server.infrastructure.db.mapper.InterfaceInfoMapper;
import cn.bby.query.server.infrastructure.db.mapper.InterfaceSubTableMapper;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.generator.SnowflakeGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.map.MapLoader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author ben
 * @since 2025/4/18 23:23
 */
@Slf4j
@Component
public class InterfaceInfoRepositoryImpl implements InterfaceInfoRepository {
    private static final String LOCK_PREFIX = "interface-load-lock-";
    final SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator();
    private final RLock queryFailedLock;
    private final RLock queryPrepareLock;
    private final RLocalCachedMap<Long, InterfaceInfo> interfaceCache;
    @Resource
    private InterfaceInfoMapper interfaceInfoMapper;
    @Resource
    private InterfaceColumnMapper interfaceColumnMapper;
    @Resource
    private InterfaceSubTableMapper interfaceSubTableMapper;
    @Resource
    private InterfaceInfoRepositoryImpl interfaceInfoRepository;
    @Resource
    private DsSettingsRepository dsSettingsRepository;
    @Value("${query.max-error-retry-times:3}")
    private long maxErrRetryTimes = 3;

    public InterfaceInfoRepositoryImpl(RedissonClient redissonClient) {
        queryFailedLock = redissonClient.getLock(LOCK_PREFIX + "failed");
        queryPrepareLock = redissonClient.getLock(LOCK_PREFIX + "all");
        // 缓存,不设置过期时间，断联后更新
        LocalCachedMapOptions<Long, InterfaceInfo> options = LocalCachedMapOptions.<Long, InterfaceInfo>defaults()
                .cacheSize(1000)
                .syncStrategy(LocalCachedMapOptions.SyncStrategy.UPDATE)
                .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.LOAD)
                .evictionPolicy(LocalCachedMapOptions.EvictionPolicy.NONE)
                .loader(new MapLoader<>() {
                    @Override
                    public InterfaceInfo load(Long interfaceId) {
                        InterfaceInfoPO interfaceInfoPO = interfaceInfoMapper.selectById(interfaceId);
                        if (interfaceInfoPO == null) {
                            return null;
                        }
                        return buildDto(interfaceInfoPO);
                    }

                    @Override
                    public Iterable<Long> loadAllKeys() {
                        return ChainWrappers.lambdaQueryChain(interfaceColumnMapper)
                                .select(InterfaceColumn::getInterfaceId)
                                .list()
                                .stream()
                                .map(InterfaceColumn::getInterfaceId)
                                .collect(Collectors.toSet());
                    }
                });
        interfaceCache = redissonClient.getLocalCachedMap("interface_info_cache", options);
    }

    public void reloadCache(Long interfaceId, String status) {
        InterfaceInfo dto = interfaceCache.get(interfaceId);
        if (dto != null) {
            dto.setStatus(status);
            interfaceCache.fastPut(interfaceId, dto);
        }
    }

    @Override
    public List<InterfaceInfo> getByInterfaceNames(String serviceName, List<String> names) {
        return ChainWrappers.lambdaQueryChain(interfaceInfoMapper)
                .select(InterfaceInfoPO::getInterfaceId)
                .eq(InterfaceInfoPO::getServerName, serviceName)
                .in(InterfaceInfoPO::getInterfaceName, names)
                .ne(InterfaceInfoPO::getStatus, InterfaceStatus.ARCHIVED)
                .list()
                .stream()
                .map(InterfaceInfoPO::getInterfaceId)
                .map(interfaceCache::get)
                .toList();
    }

    @Override
    public void createBatch(String serverName, List<InterfaceMetaInfo> infos) {
        List<InterfaceInfoPO> interfaceInfoPOS = new ArrayList<>(infos.size());
        List<InterfaceColumn> columns = new ArrayList<>(infos.size() * 10);
        List<InterfaceSubTable> subTables = new ArrayList<>(infos.size() * 10);
        Map<String, Long> nameIdMap = dsSettingsRepository.getAllTableNameIdMap();
        // 检查表是否存在，不存在则创建
        Set<String> notExistTable = new HashSet<>();
        for (InterfaceMetaInfo info : infos) {
            for (ColumnMetaInfo column : info.getColumns()) {
                if (!nameIdMap.containsKey(column.getTableName())) {
                    notExistTable.add(column.getTableName());
                }
            }
        }
        if (!notExistTable.isEmpty()) {
            log.info("表不存在，开始创建表:{}", notExistTable);
            dsSettingsRepository.createTableInfos(notExistTable);
            nameIdMap = dsSettingsRepository.getAllTableNameIdMap();
        }
        for (InterfaceMetaInfo newInfo : infos) {
            InterfaceInfoPO interfaceInfoPO = FastBeanUtil.copyProperties(newInfo, new InterfaceInfoPO());
            interfaceInfoPO.setInterfaceId(snowflakeGenerator.next());
            interfaceInfoPO.setMainTableId(nameIdMap.get(newInfo.getMainTableName()));
            interfaceInfoPO.setStatus(InterfaceStatus.INIT);
            for (ColumnMetaInfo columnInfo : newInfo.getColumns()) {
                InterfaceColumn column = FastBeanUtil.copyProperties(columnInfo, new InterfaceColumn());
                column.setInterfaceId(interfaceInfoPO.getInterfaceId());
                column.setTableId(nameIdMap.get(columnInfo.getTableName()));
                columns.add(column);
            }
            interfaceInfoPO.setServerName(serverName);
            interfaceInfoPOS.add(interfaceInfoPO);

            if (CollUtil.isNotEmpty(newInfo.getSubTables())) {
                for (TableMetaInfo subTable : newInfo.getSubTables()) {
                    InterfaceSubTable table = new InterfaceSubTable();
                    table.setInterfaceId(interfaceInfoPO.getInterfaceId())
                            .setTableId(nameIdMap.get(subTable.getTableName()))
                            .setIdentityField(subTable.getIdentityField())
                            .setIsComplex(subTable.getIsComplex());
                    subTables.add(table);
                }
            }
        }

        interfaceInfoRepository.saveBatch(interfaceInfoPOS, columns, subTables);
        interfaceCache.putAll(CollStreamUtil.toMap(buildDtoList(interfaceInfoPOS), InterfaceInfo::getInterfaceId, Function.identity()));
    }

    @Transactional
    public void saveBatch(List<InterfaceInfoPO> interfaceInfoPOS, List<InterfaceColumn> columns, List<InterfaceSubTable> subTables) {
        interfaceInfoMapper.insert(interfaceInfoPOS);
        interfaceColumnMapper.insert(columns);
        interfaceSubTableMapper.insert(subTables);
    }

    @Transactional
    @Override
    public void updateStatus(InterfaceInfo interfaceInfo, String status) {
        ChainWrappers.lambdaUpdateChain(interfaceInfoMapper)
                .eq(InterfaceInfoPO::getInterfaceId, interfaceInfo.getInterfaceId())
                .set(InterfaceInfoPO::getStatus, status)
                .update();
        reloadCache(interfaceInfo.getInterfaceId(), status);
    }

    @Override
    public List<InterfaceInfo> archiveOldNotArchiveInterfaceId(InterfaceInfo interfaceInfo) {
        List<Long> list = ChainWrappers.lambdaQueryChain(interfaceInfoMapper)
                .select(InterfaceInfoPO::getInterfaceId)
                .eq(InterfaceInfoPO::getServerName, interfaceInfo.getServerName())
                .eq(InterfaceInfoPO::getInterfaceName, interfaceInfo.getInterfaceName())
                .ne(InterfaceInfoPO::getInterfaceId, interfaceInfo.getInterfaceId())
                .ne(InterfaceInfoPO::getStatus, InterfaceStatus.ARCHIVED)
                .list()
                .stream()
                .map(InterfaceInfoPO::getInterfaceId)
                .toList();
        if (list.isEmpty()) {
            return Collections.emptyList();
        }
        List<InterfaceInfo> res = list.stream()
                .map(interfaceCache::get)
                .toList();
        ChainWrappers.lambdaUpdateChain(interfaceInfoMapper)
                .in(InterfaceInfoPO::getInterfaceId, list)
                .set(InterfaceInfoPO::getStatus, InterfaceStatus.ARCHIVED)
                .update();
        return res;
    }

    private InterfaceInfo buildDto(InterfaceInfoPO interfaceInfoPO) {
        InterfaceInfo dto = new InterfaceInfo();
        dto.setInterfaceId(interfaceInfoPO.getInterfaceId());
        FastBeanUtil.copyProperties(interfaceInfoPO, dto);
        List<ColumnInfo> columnInfos = FastBeanUtil.copyToList(ChainWrappers.lambdaQueryChain(interfaceColumnMapper)
                .eq(InterfaceColumn::getInterfaceId, interfaceInfoPO.getInterfaceId())
                .list(), ColumnInfo.class);
        dto.setColumns(columnInfos);
        dto.initCache();
        return dto;
    }

    @Override
    public Collection<InterfaceInfo> getByTableIds(Set<Long> tableIds) {
        return interfaceCache.values().stream()
                .filter(interfaceInfo -> tableIds.contains(interfaceInfo.getMainTableId())
                        || tableIds.stream().anyMatch(tableId -> interfaceInfo.getSubTableById(tableId) != null))
                .collect(Collectors.toList());
    }

    private List<InterfaceInfo> buildDtoList(List<InterfaceInfoPO> interfaceInfoPOS) {
        if (interfaceInfoPOS.isEmpty()) {
            return Collections.emptyList();
        }
        Set<Long> interfaceIds = interfaceInfoPOS.stream()
                .map(InterfaceInfoPO::getInterfaceId)
                .collect(Collectors.toSet());
        Map<Long, List<InterfaceColumn>> colMap = ChainWrappers.lambdaQueryChain(interfaceColumnMapper)
                .in(InterfaceColumn::getInterfaceId, interfaceIds)
                .list()
                .stream()
                .collect(Collectors.groupingBy(InterfaceColumn::getInterfaceId));
        return interfaceInfoPOS.stream()
                .map(interfaceInfoPO -> {
                    InterfaceInfo dto = FastBeanUtil.copyProperties(interfaceInfoPO, new InterfaceInfo());
                    dto.setColumns(FastBeanUtil.copyToList(colMap.get(interfaceInfoPO.getInterfaceId()), ColumnInfo.class));
                    dto.initCache();
                    return dto;
                }).collect(Collectors.toList());
    }

    @Transactional
    @Override
    public List<InterfaceInfo> popPreparedList(int limit) {
        if (limit < 1) {
            return Collections.emptyList();
        }
        try {
            if (!queryPrepareLock.tryLock(1, TimeUnit.SECONDS)) {
                return Collections.emptyList();
            }
        } catch (InterruptedException e) {
            return Collections.emptyList();
        }
        try {
            List<InterfaceInfo> dtos = ChainWrappers.lambdaQueryChain(interfaceInfoMapper)
                    .eq(InterfaceInfoPO::getStatus, InterfaceStatus.INIT)
                    .last("limit " + limit)
                    .list()
                    .stream().map(InterfaceInfoPO::getInterfaceId)
                    .map(interfaceCache::get)
                    .toList();
            if (dtos.isEmpty()) {
                return Collections.emptyList();
            }
            ChainWrappers.lambdaUpdateChain(interfaceInfoMapper)
                    .in(InterfaceInfoPO::getInterfaceId, dtos.stream().map(InterfaceInfo::getInterfaceId).collect(Collectors.toList()))
                    .set(InterfaceInfoPO::getStatus, InterfaceStatus.LOADING)
                    .update();
            // 更新缓存
            dtos.forEach(dto -> dto.setStatus(InterfaceStatus.LOADING));
            interfaceCache.putAll(CollStreamUtil.toMap(dtos, InterfaceInfo::getInterfaceId, Function.identity()));
            return dtos;
        } finally {
            queryPrepareLock.unlock();
        }
    }

    @Override
    public List<InterfaceInfo> getReadyList(int limit) {
        return ChainWrappers.lambdaQueryChain(interfaceInfoMapper)
                .select(InterfaceInfoPO::getInterfaceId)
                .eq(InterfaceInfoPO::getStatus, InterfaceStatus.READY)
                .list()
                .stream()
                .map(InterfaceInfoPO::getInterfaceId)
                .map(interfaceCache::get)
                .toList();
    }

    @Override
    public InterfaceInfo getById(Long interfaceId) {
        return interfaceCache.get(interfaceId);
    }

    /**
     * 获取失败或者超时的接口信息
     */
    @Transactional
    @Override
    public List<InterfaceInfo> popErrorOrOutTimeList() {
        if (!queryFailedLock.tryLock()) {
            return Collections.emptyList();
        }
        try {
            List<InterfaceInfoPO> interfaceInfoPOS = interfaceInfoMapper.selectList(Wrappers.lambdaQuery(InterfaceInfoPO.class)
                    .in(InterfaceInfoPO::getStatus, InterfaceStatus.FAILED)
                    .lt(InterfaceInfoPO::getErrRetryTimes, maxErrRetryTimes)
                    .or()
                    .eq(InterfaceInfoPO::getStatus, InterfaceStatus.LOADING)
                    .apply("now() - update_time > interval '30 minutes'")
            );
            if (interfaceInfoPOS.isEmpty()) {
                return Collections.emptyList();
            }
            ChainWrappers.lambdaUpdateChain(interfaceInfoMapper)
                    .in(InterfaceInfoPO::getInterfaceId, interfaceInfoPOS.stream().map(InterfaceInfoPO::getInterfaceId).collect(Collectors.toList()))
                    .set(InterfaceInfoPO::getStatus, InterfaceStatus.INIT)
                    .setSql("err_retry_times = err_retry_times + 1")
                    .update();
            List<InterfaceInfo> infos = interfaceInfoPOS.stream()
                    .map(interfaceInfoPO -> {
                        InterfaceInfo info = interfaceCache.get(interfaceInfoPO.getInterfaceId());
                        info.setStatus(InterfaceStatus.INIT);
                        info.setErrRetryTimes(info.getErrRetryTimes() + 1);
                        return info;
                    }).collect(Collectors.toList());
            interfaceCache.putAll(CollStreamUtil.toMap(infos, InterfaceInfo::getInterfaceId, Function.identity()));
            return infos;
        } finally {
            queryFailedLock.unlock();
        }
    }
}
